package git.soulbgm.service.impl;

import git.soulbgm.config.SysConfig;
import git.soulbgm.mapper.TableBackupMapper;
import git.soulbgm.service.TableBackupService;
import git.soulbgm.utils.FileUtil;
import git.soulbgm.utils.IoUtil;
import git.soulbgm.utils.ZipUtil;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 数据库表的备份服务实现类
 *
 * @author SoulBGM
 * @date 2022/12/29 16:02
 */
@Service
public class TableBackupServiceImpl implements TableBackupService {

    /**
     * 每次返回的记录行数
     */
    private static final int LIMIT = 2000;

    /**
     * 备份目录
     * System.getProperty("user.dir")       获得项目工作目录
     * System.getProperty("java.io.tmpdir") 获得系统的临时目录
     */
    private static final String BACKUP_DIRECTORY = System.getProperty("user.dir") + File.separator + "backup" + File.separator;

    /**
     * ZIP文件后缀
     */
    private static final String ZIP_FILE_SUFFIX = ".zip";

    /**
     * SQL文件后缀
     */
    private static final String SQL_FILE_SUFFIX = ".sql";

    /**
     * 插入SQL前缀
     */
    private static final String INSERT_SQL_PREFIX = "INSERT INTO ";

    /**
     * 插入SQL列名前缀
     */
    private static final String INSERT_SQL_COLUMN_PREFIX = " (";

    /**
     * 插入SQL后缀
     */
    private static final String INSERT_SQL_SUFFIX = ");";

    /**
     * 插入SQL前缀长度
     */
    private static final int INSERT_SQL_PREFIX_LENGTH = INSERT_SQL_PREFIX.length();

    /**
     * 插入SQL列名前缀长度
     */
    private static final int INSERT_SQL_COLUMN_PREFIX_LENGTH = INSERT_SQL_COLUMN_PREFIX.length();

    /**
     * 获取表名的正则表达式
     */
    private static final Pattern PATTERN = Pattern.compile(INSERT_SQL_PREFIX + ".+? \\(");

    /**
     * 表名数组
     */
    private final String[][] table_name_array;

    @Resource
    private TableBackupMapper mapper;

    public TableBackupServiceImpl(SysConfig config) {
        Map<String, String> backupTables = config.getBackupTables();
        this.table_name_array = new String[backupTables.size()][2];
        List<String> keyList = new ArrayList<>(backupTables.keySet());
        for (int i = 0; i < keyList.size(); i++) {
            String tableName = keyList.get(i);
            String comment = backupTables.get(tableName);
            table_name_array[i][0] = tableName;
            table_name_array[i][1] = comment;
        }
    }

    @Override
    public String[][] getBackupTableName() {
        return table_name_array;
    }

    @Override
    public byte[] backupTableData(List<String> tableNames) throws IOException {
        long nowDate = System.currentTimeMillis();
        String basePath = BACKUP_DIRECTORY + nowDate;
        String targetPath = BACKUP_DIRECTORY + nowDate + ZIP_FILE_SUFFIX;
        try {
            FileUtil.createFolder(basePath);
            backupTableData(tableNames, basePath);
            ZipUtil.zipFile(basePath, targetPath);
            return IoUtil.toByteArray(targetPath);
        } finally {
            FileUtil.delFile(basePath, targetPath);
        }
    }

    /**
     * 备份表数据
     *
     * @param tableNames 表名
     * @param basePath   父路径
     */
    private void backupTableData(List<String> tableNames, String basePath) {
        StringBuilder sql = new StringBuilder();
        StringBuilder values = new StringBuilder();
        for (String tableName : tableNames) {
            int offset = 0;
            while (true) {
                boolean flag = false;
                String sqlPath = basePath + File.separator + tableName + SQL_FILE_SUFFIX;
                List<LinkedHashMap<String, Object>> list = select(tableName, LIMIT, offset);
                if (list.isEmpty()) {
                    writer(sqlPath, sql);
                    break;
                }
                sql.append(INSERT_SQL_PREFIX).append(tableName).append(INSERT_SQL_COLUMN_PREFIX);
                for (Map<String, Object> map : list) {
                    Set<String> keySet = map.keySet();
                    values.append("(");
                    for (String k : keySet) {
                        if (!flag) {
                            sql.append(k).append(",");
                        }
                        Object v = map.get(k);
                        if (v != null) {
                            values.append("'").append(v).append("'").append(",");
                        } else {
                            values.append("null").append(",");
                        }
                    }
                    if (values.length() > 1) {
                        values.deleteCharAt(values.length() - 1);
                    }
                    values.append("),");
                    flag = true;
                }
                sql.deleteCharAt(sql.length() - 1);
                sql.append(") VALUES ");
                values.deleteCharAt(values.length() - 1);
                sql.append(values).append(";\n");
                values.setLength(0);
                if (list.size() == LIMIT) {
                    offset = offset + LIMIT;
                } else {
                    writer(sqlPath, sql);
                    break;
                }
            }
        }
    }

    /**
     * 写文件
     *
     * @param sqlPath sql路径
     * @param sql     sql
     */
    private void writer(String sqlPath, StringBuilder sql) {
        IoUtil.writer(sqlPath, sql.toString());
        sql.setLength(0);
    }

    @Override
    public void recoveryTableData(InputStream inputStream) throws IOException {
        long nowDate = System.currentTimeMillis();
        String basePath = BACKUP_DIRECTORY + nowDate;
        File baseDirFile = new File(basePath);
        try {
            ZipUtil.unzip(inputStream, basePath);
            File[] files = baseDirFile.listFiles();
            if (files == null) {
                return;
            }
            for (File f : files) {
                String sql = IoUtil.getFileContent(f);
                if (sql == null) {
                    continue;
                }
                delete(sql);
                insert(sql);
            }
        } finally {
            FileUtil.del(baseDirFile);
        }
    }

    @Override
    public List<LinkedHashMap<String, Object>> select(String tableName, int limit, int offset) {
        return mapper.select(tableName, limit, offset);
    }

    @Override
    public List<String> getTableNames(String sql) {
        Matcher matcher = PATTERN.matcher(sql);
        List<String> tableNames = new ArrayList<>(4);
        while (matcher.find()) {
            String tableName = matcher.group(0);
            tableName = tableName.substring(INSERT_SQL_PREFIX_LENGTH, tableName.length() - INSERT_SQL_COLUMN_PREFIX_LENGTH);
            tableNames.add(tableName);
        }
        return tableNames;
    }

    @Override
    public void delete(String sql) {
        getTableNames(sql).forEach(mapper::delete);
    }

    @Override
    public void insert(String sql) {
        String[] split = sql.split(";\n|;\r\n");
        Arrays.stream(split).filter(o -> !"".equals(o.trim())).map(o -> {
            if (o.endsWith(INSERT_SQL_SUFFIX)) {
                return o.substring(0, o.length() - 1);
            }
            return o;
        }).forEach(mapper::insert);
    }

}
